package com.blue.base.common.jwt;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.Scanner;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import io.jsonwebtoken.Claims;
import org.apache.commons.codec.binary.Base64;

/**
 * RSA加密工具类
 */
public class RSAUtil {
    private static Map<String, String> keyMap; // 用于封装随机产生的公钥与私钥

    static {
        // 如果是分布式项目，再加一个redis的分布式锁
        synchronized (RSAUtil.class) {
            // 1.生成秘钥
            long start = System.currentTimeMillis();
            keyMap = initRsaKeys();
            System.out.println("服务端项目启动，公私秘钥生成...." + (System.currentTimeMillis() - start));// 这个耗时还是很长的，我本地大概是1.8s
            System.out.println("写入到数据库或者redis....");
        }
    }

    public static void main(String[] args) throws Exception {
//        dataTransportCase();//测试AES的key：ABCDEFGHIJKLMNOP
        jwtCase();
    }

    /**
     * jwt登录实战
     */
    public static void jwtCase() throws Exception {
        // 1.用户登录
        System.out.print("1.界面：用户输入用户名和密码：");
        Scanner scanner = new Scanner(System.in);
        String account = scanner.nextLine();
        String privateKey = keyMap.get("private");
        System.out.println("2.后台：判断用户存在，生成token给到前端....");
        String token = JwtUtil.generateToken(account, "1000", privateKey);
        // 2.token校验
        System.out.print("3.前端：新发起一个请求，header中携带了token信息：" + token);
        String publicKey = keyMap.get("public");
        Claims playLoad = JwtUtil.getPlayLoad(token, publicKey);
        System.out.print("\n4.后台：解析前端在header中携带的token信息中的playLoad：");
        System.out.println(JSON.toJSONString(playLoad));
        System.out.println("5.后台：通过用户名去redis拿登录用户的缓存信息....");
    }

    /**
     * RSA+AES加密传输实战
     */
    public static void dataTransportCase() throws Exception {
        // 1.项目启动
        String publicKey = keyMap.get("public");
        String privateKey = keyMap.get("private");
        // 2.用户输入密码
        Scanner scanner = new Scanner(System.in);
        System.out.print("1.界面：用户输入用户名和密码：");
        String userInfo = scanner.nextLine();
        System.out.println("2.全局约定：请设置一个登录相关的AES的加密key（也可以直接通过api从后台拿）：");
        String aesKey = scanner.next();
        // 3.前端将密码进行AES加密
        String aesEncryptPwd = AESUtil.encrypt(aesKey, userInfo);
        System.out.println("3.前端：对用户输入的密码加密结果传递给后台:" + aesEncryptPwd);
        // 4.前端将加密的AES密码，再使用RSA的公钥进行加密
        String rsaEncryptData = encrypt(aesEncryptPwd, publicKey);
        // 5.后台直接通过项目启动时生成的RSA公私秘钥对，使用私钥进行解密
        String rsaDecryptData = decrypt(rsaEncryptData, privateKey);
        System.out.println("4.后台：接收到经过RSA加密的AES数据,先对其使用RSA秘钥解密...." + rsaDecryptData);
        String aesDecryptPwd = AESUtil.decrypt(aesKey, rsaDecryptData);
        System.out.println("5.后台：RSA解密后，再使用AES对密码解密...." + aesDecryptPwd);
        System.out.println(aesDecryptPwd);
    }

    /**
     * 模拟项目启动生成密钥对
     * - 1.直接本地生成一个，丢到redis或者存储到文件里面，再存入到redis【不然服务挂了公私钥没了】
     * - 2.项目启动的时候，直接读redis的数据，如果没有值则直接生成一对公私密钥，用base64进行编码（公司秘钥的获取可以提供一个api接口被前端获取）
     * - 3.此处对公私秘钥用了一个base64加解密，其实没什么影响，只是做一下肉眼的保护而已，防止有心人直接拿去乱来
     */
    public static Map<String, String> initRsaKeys() {
        Map<String, String> result = Maps.newHashMap();
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        assert keyPairGen != null;
        keyPairGen.initialize(1024, new SecureRandom());
        // 生成一个密钥对，保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
        String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
        result.put("public", publicKeyString);
        result.put("private", privateKeyString);
        return result;
    }

    /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     */
    public static String encrypt(String str, String publicKey) {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = null;
        String outStr = null;
        try {
            pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
        } catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException |
                 NoSuchPaddingException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //RSA加密
        return outStr;
    }


    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     * @return 明文
     */
    public static String decrypt(String str, String privateKey) {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey;
        //RSA解密
        Cipher cipher;
        String outStr = null;
        try {
            priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            outStr = new String(cipher.doFinal(inputByte));
        } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException |
                 IllegalBlockSizeException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return outStr;
    }

    /**
     * 公钥获取
     */
    public static PublicKey getPublicKey(byte[] publicKey) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }

    /**
     * 私钥获取
     */
    public static PrivateKey getPrivateKey(byte[] privateKey) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKey);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }
}

